// 版权所有2009年围棋作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package gc

import (
	"bufio"
	"bytes"
	"cmd/compile/internal/base"
	"cmd/compile/internal/deadcode"
	"cmd/compile/internal/devirtualize"
	"cmd/compile/internal/dwarfgen"
	"cmd/compile/internal/escape"
	"cmd/compile/internal/inline"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/logopt"
	"cmd/compile/internal/noder"
	"cmd/compile/internal/pkginit"
	"cmd/compile/internal/reflectdata"
	"cmd/compile/internal/ssa"
	"cmd/compile/internal/ssagen"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/dwarf"
	"cmd/internal/obj"
	"cmd/internal/objabi"
	"cmd/internal/src"
	"flag"
	"fmt"
	"internal/buildcfg"
	"log"
	"os"
	"runtime"
)

func hidePanic() {
	if base.Debug.Panic == 0 && base.Errors() > 0 {
		// 如果我们已经抱怨过了
		// 在节目中，不要抱怨
		// 关于恐慌；让用户进行清理
		// 请输入代码，然后重试。
		if err := recover(); err != nil {
			if err == "-h" {
				panic(err)
			}
			base.ErrorExit()
		}
	}
}

// Main解析命令行中指定的标志和Go源文件
// 参数，类型检查已解析的Go包，将函数编译到计算机
// 代码，最后将编译后的包定义写入磁盘。
func Main(archInit func(*ssagen.ArchInfo)) {
	base.Timer.Start("fe", "init")

	defer hidePanic()

	archInit(&ssagen.Arch)

	base.Ctxt = obj.Linknew(ssagen.Arch.LinkArch)
	base.Ctxt.DiagFunc = base.Errorf
	base.Ctxt.DiagFlush = base.FlushErrors
	base.Ctxt.Bso = bufio.NewWriter(os.Stdout)

	// UseBASEntries是首选，因为它减少了大约2%的构建时间，但是LLDB、dsymutil和dwarfdump
	// 关于达尔文，我们不能正确地支持它，特别是自从Macos10.14（莫哈韦）以来。这将作为一个标志公开
	// 允许在Linux上使用LLVM工具进行测试，并帮助向LLVM项目报告此错误。
	// 参见错误31188和21945（CLs 170638、98075、72371）。
	base.Ctxt.UseBASEntries = base.Ctxt.Headtype != objabi.Hdarwin

	types.LocalPkg = types.NewPkg("", "")
	types.LocalPkg.Prefix = "\"\""

	// 直到导入之后，我们才知道localpkg的高度
	// 处理。同时，设置为MaxPkgHeight以确保
	// 至少在那之前，身高比较是有效的。
	types.LocalPkg.Height = types.MaxPkgHeight

	// 伪包，用于作用域
	types.BuiltinPkg = types.NewPkg("go.builtin", "") // TODO（gri）将此包命名为go.builtin？
	types.BuiltinPkg.Prefix = "go.builtin"            // 无法进入%2内置

	// 伪包，由导入访问“不安全”
	ir.Pkgs.Unsafe = types.NewPkg("unsafe", "unsafe")

	// 包含编译器内置代码的伪包
	// 包运行时的声明。这些是在一份声明中声明的
	// 分离包以避免与包运行时的冲突
	// 实际声明，可能有意不同，但
	// 微不足道的。
	ir.Pkgs.Runtime = types.NewPkg("go.runtime", "runtime")
	ir.Pkgs.Runtime.Prefix = "runtime"

	// 符号表中使用的伪包
	ir.Pkgs.Itab = types.NewPkg("go.itab", "go.itab")
	ir.Pkgs.Itab.Prefix = "go.itab" // 不去%2eitab

	// 用于匿名接收者方法的伪包
	ir.Pkgs.Go = types.NewPkg("go", "")

	base.DebugSSA = ssa.PhaseOption
	base.ParseFlags()

	// 记录影响生成结果的标志。（而且不要
	// 记录不需要的标志，因为这会导致伪错误
	// 二进制文件中的更改。）
	dwarfgen.RecordFlags("B", "N", "l", "msan", "race", "shared", "dynlink", "dwarf", "dwarflocationlists", "dwarfbasentries", "smallframes", "spectre")

	if !base.EnableTrace && base.Flag.LowerT {
		log.Fatalf("compiler not built with support for -t")
	}

	// 启用内联（在RecordFlags之后，以避免记录重写的-l）。目前：
	// 默认值：内联打开。（Flag.LowerL==1）
	// -l：内联关闭（Flag.LowerL==0）
	// -l=2，-l=3：再次内联，并进行额外调试（Flag.LowerL>1）
	if base.Flag.LowerL <= 1 {
		base.Flag.LowerL = 1 - base.Flag.LowerL
	}

	if base.Flag.SmallFrames {
		ir.MaxStackVarSize = 128 * 1024
		ir.MaxImplicitStackVarSize = 16 * 1024
	}

	if base.Flag.Dwarf {
		base.Ctxt.DebugInfo = dwarfgen.Info
		base.Ctxt.GenAbstractFunc = dwarfgen.AbstractFunc
		base.Ctxt.DwFixups = obj.NewDwarfFixupTable(base.Ctxt)
	} else {
		// 如果根本没有矮人，请关闭内联生成
		base.Flag.GenDwarfInl = 0
		base.Ctxt.Flag_locationlists = false
	}
	if base.Ctxt.Flag_locationlists && len(base.Ctxt.Arch.DWARFRegisters) == 0 {
		log.Fatalf("location lists requested but register mapping not available on %v", base.Ctxt.Arch.Name)
	}

	types.ParseLangFlag()

	symABIs := ssagen.NewSymABIs(base.Ctxt.Pkgpath)
	if base.Flag.SymABIs != "" {
		symABIs.ReadSymABIs(base.Flag.SymABIs)
	}

	if base.Compiling(base.NoInstrumentPkgs) {
		base.Flag.Race = false
		base.Flag.MSan = false
	}

	ssagen.Arch.LinkArch.Init(base.Ctxt)
	startProfile()
	if base.Flag.Race || base.Flag.MSan {
		base.Flag.Cfg.Instrumenting = true
	}
	if base.Flag.Dwarf {
		dwarf.EnableLogging(base.Debug.DwarfInl != 0)
	}
	if base.Debug.SoftFloat != 0 {
		if buildcfg.Experiment.RegabiArgs {
			log.Fatalf("softfloat mode with GOEXPERIMENT=regabiargs not implemented ")
		}
		ssagen.Arch.SoftFloat = true
	}

	if base.Flag.JSON != "" { // 解析版本，json日志优化的目标。
		logopt.LogJsonOption(base.Flag.JSON)
	}

	ir.EscFmt = escape.Fmt
	ir.IsIntrinsicCall = ssagen.IsIntrinsicCall
	inline.SSADumpInline = ssagen.DumpInline
	ssagen.InitEnv()
	ssagen.InitTables()

	types.PtrSize = ssagen.Arch.LinkArch.PtrSize
	types.RegSize = ssagen.Arch.LinkArch.RegSize
	types.MaxWidth = ssagen.Arch.MAXWIDTH

	typecheck.Target = new(ir.Package)

	typecheck.NeedITab = func(t, iface *types.Type) { reflectdata.ITabAddr(t, iface) }
	typecheck.NeedRuntimeType = reflectdata.NeedRuntimeType // TODO（rsc）：键入SYM表示锁？

	base.AutogeneratedPos = makePos(src.NewFileBase("<autogenerated>", "<autogenerated>"), 1, 0)

	typecheck.InitUniverse()

	// 解析和类型检查输入。
	noder.LoadPackage(flag.Args())

	dwarfgen.RecordPackageName()

	// 构建初始化任务。
	if initTask := pkginit.Task(); initTask != nil {
		typecheck.Export(initTask)
	}

	// 消除一些明显死掉的代码。
	// 必须在打字检查后进行。
	for _, n := range typecheck.Target.Decls {
		if n.Op() == ir.ODCLFUNC {
			deadcode.Func(n.(*ir.Func))
		}
	}

	// 计算名称的Addrtaken。
	// 我们需要等到类型检查完成，这样当我们看到&x[i]
	// 我们知道，如果x是一个数组，那么x的地址会被获取，但如果x是一个片，则不会。
	// 我们在这里大量计算Addrtaken。
	// 在这个阶段之后，我们以增量方式维护Addrtaken。
	if typecheck.DirtyAddrtaken {
		typecheck.ComputeAddrtaken(typecheck.Target.Decls)
		typecheck.DirtyAddrtaken = false
	}
	typecheck.IncrementalAddrtaken = true

	if base.Debug.TypecheckInl != 0 {
		// 如果Debug.l>1，则类型检查导入的函数体，
		// 否则，在使用或重新导出时会延迟。
		typecheck.AllImportedBodies()
	}

	// 内联
	base.Timer.Start("fe", "inlining")
	if base.Flag.LowerL != 0 {
		inline.InlinePackage()
	}

	// 去虚拟化。
	for _, n := range typecheck.Target.Decls {
		if n.Op() == ir.ODCLFUNC {
			devirtualize.Func(n.(*ir.Func))
		}
	}
	ir.CurFunc = nil

	// 生成ABI包装。必须在逃逸分析之前发生
	// 而且不会从死编码或内联中获益。
	symABIs.GenABIWrappers()

	// 逃逸分析。
	// 将堆分配移动到堆栈上时需要，
	// 这反过来又是关闭实施所需要的，
	// 它将堆栈变量的地址存储到闭包中。
	// 如果闭包没有逃逸，它需要在堆栈上
	// 否则堆栈复印机将不会更新它。
	// 在逸出分析中，大值也会从堆栈中移出；
	// 因为大值可能包含指针，所以必须提前发生。
	base.Timer.Start("fe", "escapes")
	escape.Funcs(typecheck.Target.Decls)

	// 收集围棋信息：nowritebarrierrec
	// 检查。这必须在行走期间转换闭包之前发生
	// 我们将在写入障碍消除后进行最终检查
	// 插入。
	if base.Flag.CompilingRuntime {
		ssagen.EnableNoWriteBarrierRecCheck()
	}

	// 准备SSA汇编。
	// 这必须在CompileTabs之前，因为CompileTabs
	// 可以触发函数编译。
	typecheck.InitRuntime()
	ssagen.InitConfig()

	// 在编译之前，请编译在上找到的ITAB
	// 右边的OCONVIFACE使方法
	// 可以在编译期间进行反虚拟化。
	ir.CurFunc = nil
	reflectdata.CompileITabs()

	// 编译顶级函数。
	// 不要使用range--walk可以向Target.Decls添加函数。
	base.Timer.Start("be", "compilefuncs")
	fcount := int64(0)
	for i := 0; i < len(typecheck.Target.Decls); i++ {
		if fn, ok := typecheck.Target.Decls[i].(*ir.Func); ok {
			enqueueFunc(fn)
			fcount++
		}
	}
	base.Timer.AddEvent(fcount, "funcs")

	compileFunctions()

	if base.Flag.CompilingRuntime {
		// 写入障碍现在已为人所知。检查调用图。
		ssagen.NoWriteBarrierRecCheck()
	}

	// 完成DWARF内联例程模具，然后显式关闭
	// 缩短内联发电机，以避免产生故障
	// 方法包装器。
	if base.Ctxt.DwFixups != nil {
		base.Ctxt.DwFixups.Finalize(base.Ctxt.Pkgpath, base.Debug.DwarfInl != 0)
		base.Ctxt.DwFixups = nil
		base.Flag.GenDwarfInl = 0
	}

	// 将对象数据写入磁盘。
	base.Timer.Start("be", "dumpobj")
	dumpdata()
	base.Ctxt.NumberSyms()
	dumpobj()
	if base.Flag.AsmHdr != "" {
		dumpasmhdr()
	}

	ssagen.CheckLargeStacks()
	typecheck.CheckFuncStack()

	if len(compilequeue) != 0 {
		base.Fatalf("%d uncompiled functions", len(compilequeue))
	}

	logopt.FlushLoggedOpts(base.Ctxt, base.Ctxt.Pkgpath)
	base.ExitIfErrors()

	base.FlushErrors()
	base.Timer.Stop()

	if base.Flag.Bench != "" {
		if err := writebench(base.Flag.Bench); err != nil {
			log.Fatalf("cannot write benchmark data: %v", err)
		}
	}
}

func writebench(filename string) error {
	f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return err
	}

	var buf bytes.Buffer
	fmt.Fprintln(&buf, "commit:", buildcfg.Version)
	fmt.Fprintln(&buf, "goos:", runtime.GOOS)
	fmt.Fprintln(&buf, "goarch:", runtime.GOARCH)
	base.Timer.Write(&buf, "BenchmarkCompile:"+base.Ctxt.Pkgpath+":")

	n, err := f.Write(buf.Bytes())
	if err != nil {
		return err
	}
	if n != buf.Len() {
		panic("bad writer")
	}

	return f.Close()
}

func makePos(b *src.PosBase, line, col uint) src.XPos {
	return base.Ctxt.PosTable.XPos(src.MakePos(b, line, col))
}
